
# Meta build system for unity build and builds with shared sources between different binaries.
#
# Add executables using add_executable_shared(EXE TYPE SRC LIBS [EXTRA [INSTALL]]).
# Add libraries using add_library_shared(LIB SRC LIBS [EXTRA [INSTALL]]).
#
# Build the binaries using either separate_build(), shared_build() or unity_build().
#

# Add compile flags for specific source files
#  SOURCES Source(s) to add flags to
#  FLAG    String containing the flag(s) to add
function(add_source_flags SOURCES FLAG)
	foreach(source IN LISTS SOURCES)
		get_source_file_property(flags "${source}" COMPILE_FLAGS)
		if(flags)
			set_source_files_properties("${source}" PROPERTIES COMPILE_FLAGS "${flags} ${FLAG}")
		else()
			set_source_files_properties("${source}" PROPERTIES COMPILE_FLAGS "${FLAG}")
		endif()
	endforeach()
endfunction()

# Add compile definitions for specific source files
#  SOURCES Source(s) to add definitions to
#  ...     Compile definitions to add
function(add_source_definitions SOURCES)
	foreach(def IN LISTS ARGN)
		if(def MATCHES "^([/-]D)?([a-zA-Z0-9_]+(=.*)?)$")
			set_property(SOURCE ${SOURCES} APPEND PROPERTY COMPILE_DEFINITIONS ${CMAKE_MATCH_2})
		else()
			add_source_flags("${SOURCES}" "${def}")
		endif()
	endforeach()
endfunction()

function(get_short_filename variable file)
	get_filename_component(file "${file}" ABSOLUTE)
	get_source_file_property(is_generated "${file}" GENERATED)
	if(is_generated)
		file(RELATIVE_PATH file "${PROJECT_BINARY_DIR}" "${file}")
		set(file "build/${file}")
	else()
		file(RELATIVE_PATH file "${PROJECT_SOURCE_DIR}" "${file}")
	endif()
	set(${variable} "${file}" PARENT_SCOPE)
endfunction()

# Create a unity build file for the binary UB_SUFFIX with the sources stored in the
# variable named by SOURCE_VARIABLE_NAME. The name of the resulting unity build file
# will be stored in the variable named by SOURCE_VARIABLE_NAME.
function(enable_unity_build UB_SUFFIX SOURCE_VARIABLE_NAME)
	set(files ${${SOURCE_VARIABLE_NAME}})
	
	# Sort the file list to ensure we get the same files order on all machines
	list(SORT files)
	
	# Generate a unique filename for the unity build translation unit
	set(unit_build_file ${CMAKE_CURRENT_BINARY_DIR}/ub_${UB_SUFFIX}.cpp)
	
	# Open the ub file
	file(WRITE ${unit_build_file} "// Unity build generated by CMake\n\n")
	
	if(MSVC)
		set(compile_flags "/bigobj")
	else()
		set(compile_flags "")
	endif()
	set(compile_definitions)
	
	# Add include statement for each translation unit
	list(LENGTH files numfiles)
	set(currentIdx 1)
	foreach(source_file IN LISTS files)
		
		if(source_file MATCHES "\\.(rc|mm|manifest|c)$")
			
			# .rc, .mm, .manifest and .c files need to be compiled separately
			
		else()
			
			# Exclude all translation units from compilation
			set_source_files_properties("${source_file}" PROPERTIES HEADER_FILE_ONLY true)
			
			get_source_file_property(source_compile_flags "${source_file}" COMPILE_FLAGS)
			if(source_compile_flags)
				set(compile_flags "${compile_flags} ${source_compile_flags}")
			endif()
			get_source_file_property(source_compile_defs "${source_file}" COMPILE_DEFINITIONS)
			if(source_compile_defs)
				list(APPEND compile_definitions ${source_compile_defs})
			endif()
			
			get_filename_component(source_file "${source_file}" ABSOLUTE)
			
			if(MSVC OR ${CMAKE_CXX_COMPILER} MATCHES "(^|/)icp?c$")
				string(REGEX REPLACE ".*\\/" "" short_file "${source_file}")
				file(APPEND ${unit_build_file} "#pragma message"
				     " (\"[${currentIdx}/${numfiles}] Compiling ${short_file}...\")\n")
				math(EXPR currentIdx "${currentIdx} + 1")
			else()
				# While it's nice to see what actual source files are bing included, gcc
				# and some gcc-compatible compilers such as clang show a warning and/or
				# call stack for #pragma message.
				# This makes actual warnings much harder to notice, so we only enable
				# the message for whitelisted compilers.
			endif()
			
			get_short_filename(relative_file "${source_file}")
			if(NOT ARX_HAVE_RELATIVE_FILE)
				file(APPEND ${unit_build_file} "#undef ARX_FILE\n")
				file(APPEND ${unit_build_file} "#define ARX_FILE \"${relative_file}\"\n")
			endif()
			string(REGEX REPLACE "\\.[^\\./\\\\]*$" "" file_symbol "${relative_file}")
			string(REGEX REPLACE "^(src|build)[/\\\\]" "" file_symbol "${file_symbol}")
			string(REGEX REPLACE "[^a-zA-Z0-9]+" "_" file_symbol "${file_symbol}")
			string(REGEX REPLACE "^_+" "" file_symbol "${file_symbol}")
			file(APPEND ${unit_build_file} "#undef ARX_TRANSLATION_UNIT\n")
			file(APPEND ${unit_build_file} "#define ARX_TRANSLATION_UNIT ${file_symbol}\n")
			
			file(APPEND ${unit_build_file} "#include \"${source_file}\"\n")
			
			if(NOT ARX_HAVE_RELATIVE_FILE)
				file(APPEND ${unit_build_file} "#if !defined(ARX_FILE) || !defined(ARX_TRANSLATION_UNIT)\n")
			else()
				file(APPEND ${unit_build_file} "#if !defined(ARX_TRANSLATION_UNIT)\n")
			endif()
			file(APPEND ${unit_build_file} "#error \"Keep this check to avoid unused macro warnings.\"\n")
			file(APPEND ${unit_build_file} "#endif\n")
			file(APPEND ${unit_build_file} "\n")
			
		endif()
		
	endforeach(source_file)
	
	# Complement list of translation units with the name of ub
	set(${SOURCE_VARIABLE_NAME} ${${SOURCE_VARIABLE_NAME}} ${unit_build_file} PARENT_SCOPE)
	
	set_source_files_properties("${unit_build_file}" PROPERTIES COMPILE_FLAGS "${compile_flags}")
	set_property(SOURCE "${unit_build_file}" PROPERTY COMPILE_DEFINITIONS ${compile_definitions})
	
	# Put ub file at the root of the project
	source_group("" FILES ${unit_build_file})
	
endfunction()

set(APPLICATION_MANIFEST_RESOURCE_TEMPLATE "${CMAKE_CURRENT_LIST_DIR}/Manifest.rc.in")
set(APPLICATION_MANIFEST_RESOURCE_SCRIPT "${CMAKE_CURRENT_LIST_DIR}/ConfigureFileScript.cmake")

function(process_resource_files BINARY SOURCE_VARIABLE_NAME)
	
	if(NOT WIN32)
		# Resource files are only used on Windows
		return()
	endif()
	
	set(rc_files)
	set(generated_rc_files)
	
	# MinGW does not support manifest files - manually create the resource file
	foreach(source_file IN LISTS ${SOURCE_VARIABLE_NAME})
		
		if(source_file MATCHES "\\.manifest" AND NOT MSVC)
			get_filename_component(manifest_filename "${source_file}" NAME)
			set(manifest_rc_file "${CMAKE_CURRENT_BINARY_DIR}/${manifest_filename}.rc")
			add_custom_command(
				OUTPUT "${manifest_rc_file}"
				COMMAND ${CMAKE_COMMAND}
					"-Dmanifest=${source_file}"
					"-Dtemplate=${APPLICATION_MANIFEST_RESOURCE_TEMPLATE}" "-Doutput=${manifest_rc_file}"
					-P "${APPLICATION_MANIFEST_RESOURCE_SCRIPT}"
				DEPENDS "${APPLICATION_MANIFEST_RESOURCE_TEMPLATE}" "${source_file}"
				MAIN_DEPENDENCY ${source_file}
				COMMENT ""
				VERBATIM
			)
			set_source_files_properties("${source_file}" PROPERTIES HEADER_FILE_ONLY true)
			list(APPEND generated_rc_files "${manifest_rc_file}")
			list(APPEND rc_files ${manifest_rc_file})
		elseif(source_file MATCHES "\\.rc")
			list(APPEND rc_files ${source_file})
		endif()
		
	endforeach()
	
	# MinGW cannot link multiple resource files - merge them
	set(rc_file)
	list(LENGTH rc_files rc_files_count)
	if(NOT MSVC AND rc_files_count GREATER 1)
		
		set(rc_file ${CMAKE_CURRENT_BINARY_DIR}/${BINARY}.rc)
		file(WRITE ${rc_file} "// Resource file generated by CMake\n\n")
		
		set(compile_flags)
		set(compile_definitions)
		
		list(SORT rc_files)
		
		foreach(source_file IN LISTS rc_files)
			
			set_source_files_properties("${source_file}" PROPERTIES HEADER_FILE_ONLY true)
			
			get_source_file_property(source_compile_flags "${source_file}" COMPILE_FLAGS)
			if(source_compile_flags)
				set(compile_flags "${compile_flags} ${source_compile_flags}")
			endif()
			get_source_file_property(source_compile_defs "${source_file}" COMPILE_DEFINITIONS)
			if(source_compile_defs)
				list(APPEND compile_definitions ${source_compile_defs})
			endif()
			
			get_filename_component(source_file "${source_file}" ABSOLUTE)
			file(APPEND ${rc_file} "#include \"${source_file}\"\n\n")
			
		endforeach()
		
		set_source_files_properties("${rc_file}" PROPERTIES COMPILE_FLAGS "${compile_flags}")
		set_property(SOURCE "${rc_file}" PROPERTY COMPILE_DEFINITIONS ${compile_definitions})
		source_group("" FILES ${rc_file})
		
	endif()
	
	set(${SOURCE_VARIABLE_NAME} ${${SOURCE_VARIABLE_NAME}} ${generated_rc_files} ${rc_file} PARENT_SCOPE)
	
endfunction()


unset(SHARED_BUILD_BINARIES CACHE)


# Change the type of a binary.
#  BIN   Name of the binary to change the type of.
#  TYPE  The type to change the binary to.
# Valid types:
#  WIN32  Create a GUI executable.
function(set_binary_type BIN TYPE)
	set(SHARED_BUILD_${BIN}_TYPE "${TYPE}" CACHE INTERNAL "")
endfunction()


# Change the install directory of a binary.
#  BIN  Name of the binary to change the type of.
#  DIR  Where to install the binary.
function(set_binary_installdir BIN DIR)
	set(build_type "${SHARED_BUILD_${BIN}_TYPE}")
	set(SHARED_BUILD_${BIN}_INSTALL "${DIR}" CACHE INTERNAL "")
endfunction()

function(set_binary_public_headers BIN HEADERS)
	set(SHARED_BUILD_${BIN}_HEADERS "${HEADERS}" CACHE INTERNAL "")
endfunction()

# Add include directories to a binary
#  BIN  Name of the binary to add include directories to.
#  ...  Include directories to add to the binary.
function(add_binary_includes BIN)
	set(includes ${SHARED_BUILD_${BIN}_INCLUDES})
	list(APPEND includes ${ARGN})
	set(SHARED_BUILD_${BIN}_INCLUDES "${includes}" CACHE INTERNAL "")
endfunction()

function(set_binary_version BIN VERSION)
	set(SHARED_BUILD_${BIN}_VERSION "${VERSION}" CACHE INTERNAL "")
	if(ARGC GREATER 2)
		set(SHARED_BUILD_${BIN}_SOVERSION "${ARGV2}" CACHE INTERNAL "")
	endif()
endfunction()

function(add_binary_symlinks BIN)
	set(symlinks ${SHARED_BUILD_${BIN}_SYMLINKS})
	list(APPEND symlinks ${ARGN})
	set(SHARED_BUILD_${BIN}_SYMLINKS "${symlinks}" CACHE INTERNAL "")
endfunction()

function(_add_binary_shared BIN TYPE SRC LIBS EXTRA INSTALLDIR)
	list(REMOVE_DUPLICATES SRC)
	list(REMOVE_DUPLICATES LIBS)
	if($ENV{PORTAGE_REPO_NAME} MATCHES "gentoo")
		# Meh
		unset(LIBS)
	endif()
	set_binary_type("${BIN}" "${TYPE}")
	set(SHARED_BUILD_${BIN}_SOURCES "${SRC}" CACHE INTERNAL "")
	set(SHARED_BUILD_${BIN}_LIBS "${LIBS}" CACHE INTERNAL "")
	set(SHARED_BUILD_${BIN}_EXTRA "${EXTRA}" CACHE INTERNAL "")
	set(SHARED_BUILD_${BIN}_HEADERS "" CACHE INTERNAL "")
	set(SHARED_BUILD_${BIN}_INCLUDES "" CACHE INTERNAL "")
	set(SHARED_BUILD_${BIN}_VERSION "" CACHE INTERNAL "")
	set(SHARED_BUILD_${BIN}_SOVERSION "" CACHE INTERNAL "")
	set(SHARED_BUILD_${BIN}_SYMLINKS "" CACHE INTERNAL "")
	set_binary_installdir("${BIN}" "${INSTALLDIR}")
	set(SHARED_BUILD_BINARIES ${SHARED_BUILD_BINARIES} ${BIN} CACHE INTERNAL "")
endfunction()


# Add an executable to be build by either separate_build(), shared_build() or unity_build()
#  EXE   Name of the executable to add.
#  SRC   The executable's source files.
#  LIBS  Libraries to link the executable against.
#  EXTRA Additional arguments to pass to add_executable() but not shared with
#        other binaries or included in unity builds.
function(add_executable_shared EXE SRC LIBS)
	if(ARGC GREATER 3)
		set(extra "${ARGV3}")
	else()
		set(extra "")
	endif()
	if(DEFINED CMAKE_INSTALL_BINDIR)
		set(installdir "${CMAKE_INSTALL_BINDIR}")
	else()
		set(installdir bin)
	endif()
	_add_binary_shared("${EXE}" "" "${SRC}" "${LIBS}" "${extra}" "${installdir}")
endfunction()


# Add a library to be build by either separate_build(), shared_build() or unity_build()
#  LIB   Name of the library to add.
#  SRC   The librarie's source files.
#  LIBS  Libraries to link the library against.
#  EXTRA Additional arguments to pass to add_library() but not shared with
#        orther binaries or included in unity builds.
function(add_library_shared LIB SRC LIBS)
	if(ARGC GREATER 3)
		set(extra "${ARGV3}")
	else()
		set(extra "")
	endif()
	if(DEFINED CMAKE_INSTALL_LIBDIR)
		set(installdir "${CMAKE_INSTALL_LIBDIR}")
	else()
		set(installdir lib)
	endif()
	_add_binary_shared("${LIB}" SHARED "${SRC}" "${LIBS}" "${extra}" "${installdir}")
endfunction()


# Calculate the intersection of the lists SRC1 and SRC2 and store the result in the variable named by DEST.
function(intersect DEST SRC1 SRC2)
	
	set(dest "")
	
	foreach(src IN LISTS SRC1)
		list(FIND SRC2 "${src}" found)
		if(NOT found EQUAL -1)
			list(APPEND dest "${src}")
		endif()
	endforeach()
	
	set(${DEST} "${dest}" PARENT_SCOPE)
	
endfunction()


function(_shared_build_helper LIB LIST BINARIES FIRST)
	
	set(list ${LIST})
	set(first ${FIRST})
	
	# Find common sources and extract object libraries.
	foreach(bin IN LISTS LIST)
		
		list(REMOVE_ITEM list ${bin})
		
		set(binaries ${BINARIES} ${bin})
		
		_shared_build_helper(${LIB}_${bin} "${list}" "${binaries}" ${first})
		
		set(first 0)
		
	endforeach()
	
	# Find sources common to all binaries in the current set.
	set(first 1)
	foreach(bin IN LISTS BINARIES)
		if(first)
			set(first 0)
			set(common_src "${SHARED_BUILD_${bin}_SOURCES}")
			set(common_inc "${SHARED_BUILD_${bin}_INCLUDES}")
		else()
			intersect(common_src "${common_src}" "${SHARED_BUILD_${bin}_SOURCES}")
			intersect(common_inc "${common_inc}" "${SHARED_BUILD_${bin}_INCLUDES}")
		endif()
	endforeach()
	
	if(WIN32 AND NOT MSVC)
		# Resource files need to be merged for MinGW - never split them up into separate targets
		set(common_src_copy ${common_src})
		foreach(source_file IN LISTS common_src_copy)
			if(source_file MATCHES "\\.(rc|manifest)$")
				list(REMOVE_ITEM common_src ${src})
			endif()
		endforeach()
	endif()
	
	# We found common sources!
	if(NOT "${common_src}" STREQUAL "")
		
		list(LENGTH LIST binaries_remaining)
		if(FIRST AND binaries_remaining EQUAL 0)
			set(lib common)
		else()
			set(lib _${LIB}_common)
		endif()
		
		foreach(source_file IN LISTS common_src)
			if(NOT source_file MATCHES "\\.(rc|manifest)$")
				get_short_filename(relative_file "${source_file}")
				if(NOT ARX_HAVE_RELATIVE_FILE)
					set_property(SOURCE "${source_file}" APPEND PROPERTY COMPILE_DEFINITIONS
					             "ARX_FILE=\"${relative_file}\"")
				endif()
			endif()
		endforeach()
		
		# Add a new library for the common sources
		add_library(${lib} OBJECT ${common_src})
		
		_add_dependencies_for_shared_generated_files(${lib} "${common_src}")
		
		set(is_shared_lib 0)
		
		# Remove sources from binaries and link the library instead.
		foreach(bin IN LISTS BINARIES)
			
			set(build_type "${SHARED_BUILD_${bin}_TYPE}")
			if(build_type STREQUAL "SHARED")
				set(is_shared_lib 1)
			endif()
			
			set(uncommon_src "${SHARED_BUILD_${bin}_SOURCES}")
			foreach(src IN LISTS common_src)
				list(REMOVE_ITEM uncommon_src ${src})
			endforeach()
			set(SHARED_BUILD_${bin}_SOURCES "${uncommon_src}" CACHE INTERNAL "")
			
			set(SHARED_BUILD_${bin}_EXTRA $<TARGET_OBJECTS:${lib}>
			    "${SHARED_BUILD_${bin}_EXTRA}" CACHE INTERNAL "")
			
		endforeach()
		
		if(is_shared_lib)
			set_target_properties(${lib} PROPERTIES POSITION_INDEPENDENT_CODE ON)
		endif()
		
		target_include_directories(${lib} SYSTEM PRIVATE ${common_inc})
		
	endif()
	
endfunction()


function(_shared_build_cleanup)
	
	foreach(bin IN LISTS SHARED_BUILD_BINARIES)
		unset(SHARED_BUILD_${bin}_TYPE CACHE)
		unset(SHARED_BUILD_${bin}_SOURCES CACHE)
		unset(SHARED_BUILD_${bin}_LIBS CACHE)
		unset(SHARED_BUILD_${bin}_EXTRA CACHE)
		unset(SHARED_BUILD_${bin}_INSTALL CACHE)
		unset(SHARED_BUILD_${bin}_HEADERS CACHE)
	endforeach()
	
	unset(SHARED_BUILD_BINARIES CACHE)
	
endfunction()

function(_target_for_generated_source SRC VAR)
	file(RELATIVE_PATH relative_path ${CMAKE_BINARY_DIR} ${SRC})
	string(REGEX REPLACE "[_\\./]+" "_" target_suffix "${relative_path}")
	set(${VAR} "generate_${target_suffix}" PARENT_SCOPE)
endfunction()

# Create targets for generated sources used in multiple binaries.
# This is so that we can add target-level dependencies between the binaries and the source to
# prevent the source being generated multiple times in parallel (which can fail).
# https://cmake.org/pipermail/cmake/2008-October/024492.html
function(_create_targets_for_shared_generated_files)
	
	set(all_sources)
	
	foreach(bin IN LISTS SHARED_BUILD_BINARIES)
		foreach(src IN LISTS SHARED_BUILD_${bin}_SOURCES SHARED_BUILD_${bin}_INCLUDES)
			get_source_file_property(is_generated ${src} GENERATED)
			if(is_generated)
				list(FIND all_sources "${src}" found)
				if(found EQUAL -1)
					# Not used by any other binaries so far
				else()
					_target_for_generated_source("${src}" target)
					if(NOT TARGET ${target})
						add_custom_target(${target} DEPENDS ${src})
					endif()
				endif()
			endif()
		endforeach()
		list(APPEND all_sources ${SHARED_BUILD_${bin}_SOURCES} ${SHARED_BUILD_${bin}_INCLUDES})
	endforeach()
	
endfunction()

function(_add_dependencies_for_shared_generated_files TARGET SOURCES)
	foreach(src IN LISTS SOURCES)
		get_source_file_property(is_generated ${src} GENERATED)
		if(is_generated)
			_target_for_generated_source("${src}" generate_target)
			if(TARGET ${generate_target})
				add_dependencies(${TARGET} ${generate_target})
			endif()
		endif()
	endforeach()
endfunction()

function(_shared_build_add_binary bin)
	
	set(all_sources ${SHARED_BUILD_${bin}_SOURCES} ${SHARED_BUILD_${bin}_HEADERS})
	
	process_resource_files(${bin} SHARED_BUILD_${bin}_SOURCES)
	
	set(build_type "${SHARED_BUILD_${bin}_TYPE}")
	if(build_type STREQUAL "SHARED")
		add_library(
			${bin} ${SHARED_BUILD_${bin}_TYPE}
			${SHARED_BUILD_${bin}_SOURCES}
			${SHARED_BUILD_${bin}_EXTRA}
		)
	else()
		add_executable(
			${bin} ${SHARED_BUILD_${bin}_TYPE}
			${SHARED_BUILD_${bin}_SOURCES}
			${SHARED_BUILD_${bin}_EXTRA}
		)
	endif()
	
	_add_dependencies_for_shared_generated_files(${bin} "${all_sources}")
	
	target_link_libraries(${bin} ${SHARED_BUILD_${bin}_LIBS})
	
	if(NOT SHARED_BUILD_${bin}_HEADERS STREQUAL "")
		set_target_properties(${bin} PROPERTIES PUBLIC_HEADER "${SHARED_BUILD_${bin}_HEADERS}")
	endif()
	
	if(NOT SHARED_BUILD_${bin}_INCLUDES STREQUAL "")
		target_include_directories(${bin} SYSTEM PRIVATE ${SHARED_BUILD_${bin}_INCLUDES})
	endif()
	
	if(NOT SHARED_BUILD_${bin}_INSTALL STREQUAL "")
		set(install ${SHARED_BUILD_${bin}_INSTALL})
		if(NOT install STREQUAL "")
			if(build_type STREQUAL "SHARED")
				set(install
					LIBRARY DESTINATION "${install}"
					ARCHIVE DESTINATION "${install}"
					RUNTIME DESTINATION "${install}"
					PUBLIC_HEADER DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
				)
			else()
				set(install RUNTIME DESTINATION "${install}")
			endif()
		endif()
		install(TARGETS ${bin} ${install})
	endif()
	
	if(MACOS)
		# For macOS, CMake maps VERSION to the -current_version linker property
		# and SOVERSION to *both* the ABI version in the file name and to the 
		# -compatibility_version linker property.
		# The -current_version and -compatibility_version have restrictions
		# to the size and number of the version components and must be 
		# comparable to each other: The run-time -current_version must be
		# greater than or eaqual to the link-time -compatibility_version.
		# This is not guaranteed by VERSION/SOVERSION values intended for
		# ELF systems such as Linux.
		# On the other hand, ELF (and Windows) does not support a concept like
		# -compatibility_version.
		# Disable the check on macOS as well by not setting the VERSION property.
	elseif(NOT SHARED_BUILD_${bin}_VERSION STREQUAL "")
		set_target_properties(${bin} PROPERTIES VERSION "${SHARED_BUILD_${bin}_VERSION}")
	endif()
	if(NOT SHARED_BUILD_${bin}_SOVERSION STREQUAL "")
		set(soversion "${SHARED_BUILD_${bin}_SOVERSION}")
		if(MACOS)
			# CMake treats version number 0 as special for macOS builds.
			math(EXPR soversion "${soversion} + 1")
		endif()
		set_target_properties(${bin} PROPERTIES SOVERSION "${soversion}")
	endif()
	
	set(bindir "$<TARGET_FILE_DIR:${bin}>")
	set(binfile "$<TARGET_FILE_NAME:${bin}>")
	
	foreach(symlink IN LISTS SHARED_BUILD_${bin}_SYMLINKS)
		if(build_type STREQUAL "SHARED")
			set(name "${CMAKE_SHARED_LIBRARY_PREFIX}${symlink}${CMAKE_SHARED_LIBRARY_SUFFIX}")
		else()
			set(name "${symlink}${CMAKE_EXECUTABLE_SUFFIX}")
		endif()
		add_custom_command(
			TARGET ${bin} POST_BUILD
			COMMAND ${CMAKE_COMMAND} -E remove "${bindir}/${name}"
			COMMAND ${CMAKE_COMMAND} -E create_symlink "${binfile}" "${bindir}/${name}"
			COMMENT ""
			VERBATIM
		)
		if(NOT SHARED_BUILD_${bin}_INSTALL STREQUAL "")
			set(installdir bin)
			if(DEFINED CMAKE_INSTALL_BINDIR)
				set(installdir "${CMAKE_INSTALL_BINDIR}")
			endif()
			set(absinstalldir "${installdir}")
			if(NOT IS_ABSOLUTE ${absinstalldir})
				set(absinstalldir "${CMAKE_INSTALL_PREFIX}/${absinstalldir}")
			endif()
			set(absbinpath "${SHARED_BUILD_${bin}_INSTALL}/${binfile}")
			if(NOT IS_ABSOLUTE ${absbinpath})
				set(absbinpath "${CMAKE_INSTALL_PREFIX}/${absbinpath}")
			endif()
			file(RELATIVE_PATH relbinpath "${absinstalldir}" "${absbinpath}")
			add_custom_command(
				TARGET ${bin} POST_BUILD
				COMMAND ${CMAKE_COMMAND} -E remove "${bindir}/installed_${name}"
				COMMAND ${CMAKE_COMMAND} -E create_symlink "${relbinpath}" "${bindir}/installed_${name}"
				COMMENT ""
				VERBATIM
			)
			install(PROGRAMS "${bindir}/installed_${name}" DESTINATION "${installdir}" RENAME "${name}")
		endif()
	endforeach()
	
endfunction()

# Build each binary separately.
function(separate_build)
	
	_create_targets_for_shared_generated_files()
	
	foreach(bin IN LISTS SHARED_BUILD_BINARIES)
		foreach(source_file IN LISTS SHARED_BUILD_${bin}_SOURCES)
			if(NOT source_file MATCHES "\\.(rc|manifest)$")
				get_short_filename(relative_file "${source_file}")
				if(NOT ARX_HAVE_RELATIVE_FILE)
					set_property(SOURCE "${source_file}" APPEND PROPERTY COMPILE_DEFINITIONS
					             "ARX_FILE=\"${relative_file}\"")
				endif()
			endif()
		endforeach()
		_shared_build_add_binary(${bin})
	endforeach()
	
	_shared_build_cleanup()
	
endfunction()


# Build each source file separately and extract common source files into static libraries.
function(shared_build)
	
	_create_targets_for_shared_generated_files()
	
	set(list1 ${SHARED_BUILD_BINARIES})
	
	set(first 1)
	
	# Find common sources and extract static libraries.
	foreach(bin1 IN LISTS SHARED_BUILD_BINARIES)
		list(REMOVE_ITEM list1 ${bin1})
		set(list2 ${list1})
		# Require two source sets before calling _shared_build_helper so we don't
		# create static libraries for individual binaries!
		foreach(bin2 IN LISTS list1)
			list(REMOVE_ITEM list2 ${bin2})
			set(binaries ${bin1} ${bin2})
			_shared_build_helper(${bin1}_${bin2} "${list2}" "${binaries}" ${first})
			set(first 0)
		endforeach()
	endforeach()
	
	separate_build()
	
endfunction()


# Build each binary by including all the source files into one big master file.
function(unity_build)
	
	_create_targets_for_shared_generated_files()
	
	if(CMAKE_GENERATOR MATCHES "(Makefiles|Ninja)")
		add_custom_target(ub_notice
			COMMAND "${CMAKE_COMMAND}" -E echo_append
			COMMENT "Note: The unity build binaries may take a long time to compile, without any indication of progress. Be patient."
		)
	endif()
	
	foreach(bin IN LISTS SHARED_BUILD_BINARIES)
		enable_unity_build(${bin} SHARED_BUILD_${bin}_SOURCES)
		_shared_build_add_binary(${bin})
		if(TARGET ub_notice)
			add_dependencies(${bin} ub_notice)
		endif()
	endforeach()
	
	_shared_build_cleanup()
	
endfunction()
